using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;

using SharpGL.Media3D;
using SharpGL.SceneGraph.Lighting;
using SharpGL.SceneGraph.Raytracing;
using SharpGL.SceneGraph.Helpers;
using SharpGL.SceneGraph.Transformations;
using SharpGL.SceneGraph.Assets;
using SharpGL.OpenGLAttributes;

namespace SharpGL.SceneGraph.Primitives
{
	/// <summary>
	/// a polygon contains a set of 'faces' which are indexes into a single list
	/// of vectors. The main thing about polygons is that they are easily editable
	/// by the user, depending on the Context they're in.
	/// </summary>
	[DataContract(IsReference = true)]
	public class Polygon : 
        ContextualSceneElement,
        IRenderable,
        IHasObjectSpace,
        IRayTracable,
        IFreezable,
        IVolumeBound,
        IDeepCloneable<Polygon>,
        IHasMaterial,
        IOwnOpenGLAttributesApply
    {

        #region Private Fields

        /// <summary>
        /// The IHasObjectSpace helper.
        /// </summary>
        private HasObjectSpaceHelper hasObjectSpaceHelper = new HasObjectSpaceHelper();

        /// <summary>
        /// The freezable helper.
        /// </summary>
        private FreezableHelper freezableHelper = new FreezableHelper();

        /// <summary>
        /// The bounding volume helper - used to ease implementation of IBoundVolume.
        /// </summary>
        private BoundingVolumeHelper boundingVolumeHelper = new BoundingVolumeHelper();

        /// <summary>
        /// The material.
        /// </summary>
        [DataMember]
        private Material material = null;
        
        /// <summary>
        /// The faces that make up the polygon.
        /// </summary>
        [DataMember]
        private List<Face> faces = new List<Face>();

        /// <summary>
        /// The vectors that make up the polygon.
        /// </summary>
        [DataMember]
        private ObservableCollection<Point3D> vertices = new ObservableCollection<Point3D>();

        /// <summary>
        /// The UV coordinates (texture coodinates) for the polygon.
        /// </summary>
        [DataMember]
        private List<UV> uvs = new List<UV>();

        /// <summary>
        /// The normals of the polygon object.
        /// </summary>
        [DataMember]
        private List<Vector3D> normals = new List<Vector3D>();

        /// <summary>
        /// Should the normals be drawn?
        /// </summary>
        [DataMember]
        private bool drawNormals = false;

        #endregion Private Fields

        #region Private Methods

        private void OnVerticesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
            {
                foreach (Point3D vertex in e.OldItems)
                {
                    NotifyPropertyChanged("Vertices");
                }
            }

            if (e.NewItems != null)
            {
                foreach (Point3D vertex in e.NewItems)
                {
                    NotifyPropertyChanged("Vertices");
                }
            }
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (sender is Point3D)
                NotifyPropertyChanged("Vertices[" + sender.GetType() + "]" + "." + e.PropertyName);
            else if (sender is Vector3D)
                NotifyPropertyChanged("Normals[" + sender.GetType() + "]" + "." + e.PropertyName);
            else
                NotifyPropertyChanged(sender.GetType() + "." + e.PropertyName);
        }

        /// <summary>
        /// This function tests to see if a ray interesects the polygon.
        /// </summary>
        /// <param name="ray">The ray you want to test.</param>
        /// <returns>
        /// The distance from the origin of the ray to the intersection, or -1 if there
        /// is no intersection.
        /// </returns>
        private Intersection TestIntersection(Ray ray)
        {
            Intersection intersect = new Intersection();

            //	This code came from jgt intersect_triangle code (search dogpile for it).
            foreach (Face face in faces)
            {
                //	Assert that it's a triangle.
                if (face.Count != 3)
                    continue;

                //	Find the point of intersection upon the plane, as a point 't' along
                //	the ray.
                Point3D point1OnPlane = vertices[face.Indices[0].Vertex];
                Point3D point2OnPlane = vertices[face.Indices[1].Vertex];
                Point3D point3OnPlane = vertices[face.Indices[2].Vertex];
                Point3D midpointOpp1 = (point2OnPlane + (Vector3D)point3OnPlane) / 2;
                Point3D midpointOpp2 = (point1OnPlane + (Vector3D)point3OnPlane) / 2;
                Point3D midpointOpp3 = (point1OnPlane + (Vector3D)point2OnPlane) / 2;

                Vector3D planeNormal = face.GetSurfaceNormal(this);


                Point3D diff = point1OnPlane - (Vector3D)ray.origin;
                float s1 = Vector3D.DotProduct((Vector3D)diff, planeNormal);
                float s2 = Vector3D.DotProduct(ray.direction, planeNormal);

                if (s2 == 0)
                    continue;
                float t = s1 / s2;
                if (t < 0)
                    continue;

                float denomintor = Vector3D.DotProduct(planeNormal, ray.direction);
                if (denomintor < 0.00001f && denomintor > -0.00001f)
                    continue;	//	doesn't intersect the plane.

                //	Point3D point = point1OnPlane - ray.origin;
                //	float t = (point.ScalarProduct(planeNormal)) / denomintor;

                //	Now we can get the point of intersection.
                Point3D vIntersect = ray.origin + (ray.direction * t);

                //	Do my cool test.
                Vector3D vectorTo1 = vIntersect - point1OnPlane;
                Vector3D vectorTo2 = vIntersect - point2OnPlane;
                Vector3D vectorTo3 = vIntersect - point3OnPlane;
                Vector3D vectorMidTo1 = midpointOpp1 - point1OnPlane;
                Vector3D vectorMidTo2 = midpointOpp2 - point2OnPlane;
                Vector3D vectorMidTo3 = midpointOpp3 - point3OnPlane;

                if (vectorTo1.Length > vectorMidTo1.Length)
                    continue;
                if (vectorTo2.Length > vectorMidTo2.Length)
                    continue;
                if (vectorTo3.Length > vectorMidTo3.Length)
                    continue;

                if (intersect.closeness == -1 || t < intersect.closeness)
                {
                    //	It's fucking intersection city man
                    intersect.point = vIntersect;
                    intersect.intersected = true;
                    intersect.normal = planeNormal;
                    intersect.closeness = t;
                }
            }

            return intersect;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Perform all rendering operation to the provided instance of OpenGL.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="renderMode">The render mode.</param>
        protected virtual void InternalRender(OpenGL gl, RenderMode renderMode)
        {
            if (OpenGL.IsValidContext(gl))
            {
                foreach (Face face in faces)
                {
                    // If the face has its own material, push it.
                    if (face.Material != null)
                        face.Material.Push(gl);

                    // Begin drawing a polygon.
                    switch (face.Indices.Count)
                    {
                        case 0:
                            continue;
                        case 1:
                            gl.Begin(OpenGL.GL_POINTS);
                            break;
                        case 2:
                            gl.Begin(OpenGL.GL_LINES);
                            break;
                        case 3:
                            gl.Begin(OpenGL.GL_TRIANGLES);
                            break;
                        default:
                            gl.Begin(OpenGL.GL_POLYGON);
                            break;
                    }

                    foreach (Index index in face.Indices)
                    {
                        // Set a texture coord (if any).
                        if (index.UV != -1)
                            gl.TexCoord(uvs[index.UV]);

                        if (index.Normal == -1)
                        {
                            // Do we have enough vertices for a normal?
                            if (face.Indices.Count == 2)
                            {
                                //	Create a normal.
                                Vector3D vNormal = vertices[face.Indices[1].Vertex] - vertices[face.Indices[0].Vertex];
                                vNormal.Normalize();

                                // Add it to the normals, setting the index for next time.
                                index.Normal = normals.Count;
                                normals.Add(vNormal);
                            }
                            else if (face.Indices.Count >= 3)
                            {
                                //	Create a normal.
                                Vector3D vNormal = face.GetSurfaceNormal(this);
                                vNormal.Normalize();

                                // TODO: use auto smoothing instead
                                // Add it to the normals, setting the index for next time.
                                index.Normal = normals.Count;
                                normals.Add(vNormal);
                            }
                        }


                        // Set a normal, or generate one.
                        if (index.Normal != -1)
                            gl.Normal(normals[index.Normal]);

                        // Set the point.
                        gl.Vertex(vertices[index.Vertex]);
                    }

                    gl.End();

                    // If the face has its own material, pop it.
                    if (face.Material != null)
                        face.Material.Pop(gl);
                }

                // Draw normals if we have to.
                if (drawNormals)
                {
                    // Set the colour to red.
                    gl.PushAttrib(OpenGL.GL_ALL_ATTRIB_BITS);
                    gl.Color(1, 0, 0, 1);
                    gl.Disable(OpenGL.GL_LIGHTING);

                    // Go through each face.
                    foreach (Face face in faces)
                    {
                        // Go though each index.
                        foreach (Index index in face.Indices)
                        {
                            // Make sure it's got a normal, and a point.
                            if (index.Normal != -1 && index.Vertex != -1)
                            {
                                // Get the point.
                                Point3D vertex1 = vertices[index.Vertex];

                                // Get the normal point.
                                Vector3D normal = normals[index.Normal];
                                Point3D vertex2 = vertex1 + normal;

                                gl.Begin(OpenGL.GL_LINES);
                                gl.Vertex(vertex1);
                                gl.Vertex(vertex2);
                                gl.End();
                            }
                        }
                    }

                    // Restore the attributes.
                    gl.PopAttrib();
                }
            }
        }
        //--------------------------------------------------------------------------------

        protected override void OnCreateInContext(OpenGL gl)
        {
            if (material != null && material.Texture != null)
                material.Texture.CreateInContext(gl);

            foreach (Face face in faces)
            {
                if (face.Material != null && face.Material.Texture != null)
                    face.Material.Texture.CreateInContext(gl);
            }
        }

        protected override void OnDestroyInContext(OpenGL gl)
        {
            foreach (Face face in faces)
            {
                if (face.Material != null && face.Material.Texture != null)
                    face.Material.Texture.DestroyInContext();
            }

            if (material != null && material.Texture != null)
                material.Texture.DestroyInContext();
        }

        /// <summary>
        /// This function performs lossless optimisation on the polygon, it should be 
        /// called when the geometry changes, and the polygon goes into static mode.
        /// </summary>
        /// <returns>The amount of optimisation (as a %).</returns>
        protected virtual float OptimisePolygon()
        {
            //	Check for any null faces.
            float facesBefore = faces.Count;

            for (int i = 0; i < faces.Count; i++)
            {
                if (faces[i].Count == 0)
                    faces.RemoveAt(i--);
            }
            
            float facesAfter = faces.Count;

            return (facesAfter / facesBefore) * 100;
        }

        #endregion Protected Methods

        #region Construction/Destruction

        /// <summary>
        /// Initializes a new instance of the <see cref="Polygon"/> class.
        /// </summary>
		public Polygon() 
		{
			Name = "Polygon";

            vertices.CollectionChanged += new NotifyCollectionChangedEventHandler(OnVerticesCollectionChanged);
		}

        #endregion

        #region Public Methods

        #region IRenderable interface realization

        /// <summary>
        /// Render to the provided instance of OpenGL.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="renderMode">The render mode.</param>
        public virtual void Render(OpenGL gl, RenderMode renderMode)
        {
            if (OpenGL.IsValidContext(CurrentOpenGLContext) && !OpenGL.Equals(CurrentOpenGLContext, gl))
            {
                throw new Exception("Can't render '" +
                                    Name +
                                    "' in context '" +
                                    (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                    "' - it is created in '" +
                                    (OpenGL.IsValidContext(CurrentOpenGLContext) ? CurrentOpenGLContext.Renderer : "Null") +
                                    "' OpenGL context.");
            }

            if (OpenGL.IsValidContext(gl))
            {
                //  If we're frozen, use the helper.
                if (freezableHelper.IsFrozen)
                {
                    freezableHelper.Render();
                }
                else
                {
                    InternalRender(gl, renderMode);
                }
            }
        }

        #endregion

        #region IHasObjectSpace interface realization

        /// <summary>
        /// Pushes us into Object Space using the transformation into the specified OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public void PushObjectSpace(OpenGL gl)
        {
            //  Use the helper to push us into object space.
            hasObjectSpaceHelper.PushObjectSpace(gl);
        }

        /// <summary>
        /// Pops us from Object Space using the transformation into the specified OpenGL instance.
        /// </summary>
        /// <param name="gl">The currentOpenGLContext.</param>
        public void PopObjectSpace(OpenGL gl)
        {
            //  Use the helper to pop us from object space.
            hasObjectSpaceHelper.PopObjectSpace(gl);
        }

        #endregion

        #region IRayTracable interface realization

        /// <summary>
        /// Raytraces the specified ray. If an intersection is found, it is returned,
        /// otherwise null is returned.
        /// </summary>
        /// <param name="ray">The ray.</param>
        /// <param name="scene">The scene.</param>
        /// <returns>
        /// The intersection with the object, or null.
        /// </returns>
        public Intersection Raytrace(Ray ray, Scene scene)
        {
            //	First we see if this ray intersects this polygon.
            Intersection intersect = TestIntersection(ray);

            //	If there wasn't an intersection, return.
            if (intersect.intersected == false)
                return intersect;

            // If scene don't have scene container, return.
            if (scene == null || scene.SceneContainer == null)
                return intersect;
            
            //	There was an intersection, find the color of this point on the 
            //	polygon.
            var lights = from se in scene.SceneContainer.Traverse()
                         where se is Light
                         select se;
            foreach (Light light in lights)
            {
                if (light.On)
                {
                    //	Can we see this light? Cast a shadow ray.
                    Ray shadowRay = new Ray();
                    bool shadow = false;
                    shadowRay.origin = intersect.point;
                    shadowRay.direction = light.Position - shadowRay.origin;

                    //	Test it with every polygon.
                    foreach (Polygon p in scene.SceneContainer.Traverse<Polygon>())
                    {
                        if (p == this) continue;
                        Intersection shadowIntersect = p.TestIntersection(shadowRay);
                        if (shadowIntersect.intersected)
                        {
                            shadow = true;
                            break;
                        }
                    }

                    if (shadow == false)
                    {
                        //	Now find out what this light complements to our color.
                        // TODO: float angle = light.Direction.ScalarProduct(intersect.normal);
                        // TODO: ray.light += material.CalculateLighting(light, angle);
                        ray.light.Clamp();
                    }
                }
            }

            return intersect;
        }

        #endregion

        #region IFreezable interface realization

        /// <summary>
        /// Freezes this instance using the provided OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public void Freeze(OpenGL gl)
        {
            //  Freeze using the helper.
            freezableHelper.Freeze(gl, this);
        }

        /// <summary>
        /// Unfreezes this instance using the provided OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public void Unfreeze(OpenGL gl)
        {
            //  Unfreeze using the helper.
            freezableHelper.Unfreeze();
        }

        #endregion

        #region IDeepCloneable interface realization

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// a new object that is a copy of this instance.
        /// </returns>
        public Polygon DeepClone()
        {
            //  Create a new polygon.
            Polygon polygon = new Polygon();

            //  Clone the data.
            polygon.hasObjectSpaceHelper = (HasObjectSpaceHelper)hasObjectSpaceHelper.Clone();
            polygon.freezableHelper = new FreezableHelper();
            polygon.drawNormals = drawNormals;
            polygon.material = material;
            if (material != null && material.Texture != null)
                polygon.material.Texture = (Texture2D)material.Texture.Clone();

            foreach (Point3D vertex in vertices)
                polygon.vertices.Add(new Point3D(vertex));
            foreach (UV uv in uvs)
                polygon.uvs.Add(new UV(uv));
            foreach (Vector3D normal in normals)
                polygon.normals.Add(new Vector3D(normal));
            foreach (Face face in faces)
                polygon.faces.Add(new Face(face));

            return polygon;
        }

        #endregion

        #region IOwnOpenGLAttributesApply interface realization

        /// <summary>
        /// Uses OpenGl attributes on own way.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="attributesGroup">The OpenGL attributes group.</param>
        public virtual void ApplyAttributes(OpenGL gl, OpenGLAttributesGroup attributesGroup)
        {
            if (attributesGroup != null && attributesGroup.TextureAttributes.AreAnyAttributesSet())
            {
                if (material != null && material.Texture != null)
                {
                    material.Texture.Bind(gl);
                    attributesGroup.TextureAttributes.SetAttributes(gl);
                }
                
                foreach (Face face in Faces)
                {
                    if (face.Material != null && face.Material.Texture != null)
                    {
                        face.Material.Texture.Bind(gl);
                        attributesGroup.TextureAttributes.SetAttributes(gl);
                    }
                }
            }
        }

        #endregion

        /// <summary>
		/// This function is cool, just stick in a set of points, it'll add them to the
		/// array, and create a face. It will take account of duplicate vectors too!
		/// </summary>
		/// <param name="vertexData">a set of vectors to make into a face.</param>
		public virtual void AddFaceFromVertexData(Point3D[] vertexData, float accuracy = 0.0f)
		{
			//	Create a face.
			Face newFace = new Face();

			//	Go through the vectors...
			foreach(Point3D v in vertexData)
			{
				//	Do we have this point already?
                int at = Media3DSearch.SearchPoint(vertices, 0, v, accuracy);
				
				//	Add the point, and index it.
                if (at == -1)
                {
                    newFace.Indices.Add(new Index(vertices.Count));
                    vertices.Add(v);
                }
                else
                {
                    newFace.Indices.Add(new Index(at));
                }
			}

			//	Add the face.
			faces.Add(newFace);
		}

        /// <summary>
        /// This creates a polygon from a height map (any picture). Black is low, 
        /// and the colors are high (the lighter the color, the higher the surface).
        /// </summary>
        /// <param name="filename">Path of the image file.</param>
        /// <param name="xPoints">Number of points along X.</param>
        /// <param name="yPoints">Number of points along Y.</param>
        /// <returns>True if sucessful, false otherwise.</returns>
        public virtual bool CreateFromMap(string filename, int xPoints, int yPoints)
        {
            //	Try and load the image file.
            System.Drawing.Bitmap map = new System.Drawing.Bitmap(filename);

            //  Clear lists.
            faces.Clear();
            uvs.Clear();
            normals.Clear();
            vertices.Clear();

            if (map.Size.IsEmpty)
                return false;

            //	Set the descriptive name.
            Name = "Map created from '" + filename + "'";

            //	Get points.
            for (int y = 0; y < yPoints; y++)
            {
                int yValue = (map.Height / yPoints) * y;

                for (int x = 0; x < xPoints; x++)
                {
                    int xValue = (map.Width / xPoints) * x;

                    //	Get the pixel.
                    System.Drawing.Color col = map.GetPixel(xValue, yValue);

                    float xPos = (float)x / (float)xPoints;
                    float yPos = (float)y / (float)yPoints;

                    //	Create a control point from it.
                    Point3D v = new Point3D(xPos, 0, yPos);

                    //	Add the 'height', based on color.
                    v.Y = (float)col.R / 255.0f + (float)col.G / 255.0f +
                        (float)col.B / 255.0f;

                    //	Add this point to the vectors array.
                    vertices.Add(v);
                }
            }

            //	Create faces for the polygon.
            for (int y = 0; y < (yPoints - 1); y++)
            {
                for (int x = 0; x < (xPoints - 1); x++)
                {
                    //	Create the face.
                    Face face = new Face();

                    //	Create point indicies.
                    int nTopLeft = (y * xPoints) + x;
                    int nBottomLeft = ((y + 1) * xPoints) + x;

                    face.Indices.Add(new Index(nTopLeft));
                    face.Indices.Add(new Index(nTopLeft + 1));
                    face.Indices.Add(new Index(nBottomLeft + 1));
                    face.Indices.Add(new Index(nBottomLeft));

                    // Add the face.
                    faces.Add(face);
                }
            }

            return true;
        }

        /// <summary>
        /// This function subdivides the faces of this polygon.
        /// </summary>
        /// <param name="smooth">If set to true the faces will be smoothed.</param>
        /// <returns>The number of faces in the new subdivided polygon.</returns>
        public int Subdivide()
        {
            List<Face> newFaces = new List<Face>();

            foreach (Face face in Faces)
            {
                //	Make sure the face is a triangle.
                if (face.Count != 3)
                    continue;

                //	Now get the vectors of the face.
                Point3D v1 = Vertices[face.Indices[0].Vertex];
                Point3D v2 = Vertices[face.Indices[1].Vertex];
                Point3D v3 = Vertices[face.Indices[2].Vertex];

                //	Add the vectors to get a the midpoint of the edge formed by those
                //	vectors.
                Point3D vMidpoint = (v1 + (Vector3D)v2 + (Vector3D)v3) / 3;
                Index iMidpoint = new Index(Vertices.Count);
                Vertices.Add(vMidpoint);

                //	Now make three new faces from the old vectors and the midpoint.
                Face newFace = new Face();
                newFace.Indices.Add(new Index(face.Indices[0]));
                newFace.Indices.Add(new Index(face.Indices[1]));
                newFace.Indices.Add(iMidpoint);
                newFaces.Add(newFace);

                newFace = new Face();
                newFace.Indices.Add(new Index(face.Indices[1]));
                newFace.Indices.Add(new Index(face.Indices[2]));
                newFace.Indices.Add(iMidpoint);
                newFaces.Add(newFace);

                newFace = new Face();
                newFace.Indices.Add(new Index(face.Indices[2]));
                newFace.Indices.Add(new Index(face.Indices[0]));
                newFace.Indices.Add(iMidpoint);
                newFaces.Add(newFace);
            }

            faces = newFaces;

            return faces.Count;
        }

        /// <summary>
		/// Triangulate this polygon.
		/// </summary>
        public void Triangulate()
        {
            List<Face> newFaces = new List<Face>();

            //	Go through each face...
            foreach (Face face in faces)
            {
                //	Number of triangles = vectors - 2.
                int triangles = face.Indices.Count - 2;

                //	Is it a triangle already?...
                if (triangles == 1)
                {
                    newFaces.Add(face);
                    continue;
                }

                //	Add a set of triangles.
                for (int i = 0; i < triangles; i++)
                {
                    Face triangle = new Face();
                    triangle.Indices.Add(new Index(face.Indices[0]));
                    triangle.Indices.Add(new Index(face.Indices[i + 1]));
                    triangle.Indices.Add(new Index(face.Indices[i + 2]));
                    //triangle.Indices.Add(new Index(face.Indices[i + 2]));
                    //triangle.Indices.Add(new Index(face.Indices[i + 1]));
                    newFaces.Add(triangle);
                }
            }

            faces.Clear();
            faces = newFaces;
        }

        /// <summary>
		/// Call this function as soon as you change the polygons geometry, it will
		/// re-generate normals, etc.
		/// </summary>
		/// <param name="regenerateNormals">Regenerate Normals.</param>
		public virtual void Validate(bool regenerateNormals, float accuracy = 0.001f)
		{
			if(regenerateNormals)
				normals.Clear();

			//	Go through each face.
			foreach(Face face in faces)
			{
				if(regenerateNormals)
				{
					//	Find a normal for the face.
                    Vector3D normal = face.GetSurfaceNormal(this);

					//	Does this normal already exist?
                    int index = Media3DSearch.SearchVector(normals, 0, normal, accuracy);

                    if (index == -1)
                    {
                        index = normals.Count;
                        normals.Add(normal);
                    }
					//	Set the index normal.
					foreach(Index i in face.Indices)
						i.Normal = index;
				}
			}
		}

        #endregion Public Methods

        #region Public Properties

        /// <summary>
        /// Gets the transformation that pushes us into object space.
        /// </summary>
        [Description("The Polygon Object Space Transformation"), Category("Polygon")]
        public LinearTransformation Transformation
        {
            get { return hasObjectSpaceHelper.Transformation; }
            set { hasObjectSpaceHelper.Transformation = value; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is frozen.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is frozen; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        public bool IsFrozen
        {
            get { return freezableHelper.IsFrozen; }
        }

        /// <summary>
        /// Gets the bounding volume.
        /// </summary>
        [Browsable(false)]
        public BoundingVolume BoundingVolume
        {
            get
            {
                //  todo; only create bv when vectors changed.
                boundingVolumeHelper.BoundingVolume.FromVertices(vertices);
                boundingVolumeHelper.BoundingVolume.Transform(Transformation.TransformationMatrix);
                //boundingVolumeHelper.BoundingVolume.Pad(0.1f); 
                return boundingVolumeHelper.BoundingVolume;
            }
        }

        /// <summary>
        /// Material to be used when rendering the polygon in lighted mode.
        /// This material may be overriden on a per-face basis.
        /// </summary>
        /// <value>
        /// The material.
        /// </value>
        public Material Material
        {
            get { return material; }
            set
            {
                material = value;

                NotifyPropertyChanged("Maretial");
            }
        }

        /// <summary>
        /// Gets or sets the faces.
        /// </summary>
        /// <value>
        /// The faces.
        /// </value>
		[Description("The faces that make up the polygon."), Category("Polygon")]
		public List<Face> Faces
		{
			get { return faces; }
			set
            {
                if (value != null)
                    faces = value;
                else
                    faces.Clear();

                NotifyPropertyChanged("Faces");
            }
		}

        /// <summary>
        /// Gets or sets the vectors.
        /// </summary>
        /// <value>
        /// The vectors.
        /// </value>
		[Description("The vectors that make up the polygon."), Category("Polygon")]
        public ObservableCollection<Point3D> Vertices
		{
			get { return vertices; }
			set
            {
                if (value != null)
                    vertices = value;
                else
                    vertices.Clear();

                NotifyPropertyChanged("Vertieces");
            }
		}

        /// <summary>
        /// Gets or sets the U vs.
        /// </summary>
        /// <value>
        /// The U vs.
        /// </value>
		[Description("The material coordinates."), Category("Polygon")]
		public List<UV> UVs
		{
			get { return uvs; }
            set
            {
                if (value != null)
                    uvs = value;
                else
                    uvs.Clear();

                NotifyPropertyChanged("UVs");
            }
		}

        /// <summary>
        /// Gets or sets the normals.
        /// </summary>
        /// <value>
        /// The normals.
        /// </value>
        [Description("The normals."), Category("Polygon")]
        public List<Vector3D> Normals
		{
			get { return normals; }
            set
            {
                if (value != null)
                    normals = value;
                else
                    normals.Clear();

                NotifyPropertyChanged("Normals");
            }
		}
        
        /// <summary>
        /// Gets or sets a value indicating whether [draw normals].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [draw normals]; otherwise, <c>false</c>.
        /// </value>
		[Description("Should normals be drawn for each face?"), Category("Polygon")]
		public bool DrawNormals
		{
			get { return drawNormals; }
			set
            {
                drawNormals = value;

                NotifyPropertyChanged("DrawNormals");
            }
        }

        #endregion Public Properties
    }
}
